#include <stdio.h>
#include <gstdio.h>
#include <glib-object.h>

#define entry printf("%s++\n", __FUNCTION__)
#define exit printf("%s--\n", __FUNCTION__)
#define log printf("LOG>> function: %s >> line: %d\n", __FUNCTION__, __LINE__)

typedef struct _Foo
{
    GObject parent_instance;
    gint foo_int;
}Foo;

typedef struct _FooClass
{
    GObjectClass parent_class;
}FooClass;

G_DEFINE_TYPE (Foo, foo, G_TYPE_OBJECT);

static void
foo_class_init(
        FooClass* klass
        )
{
}

static void
foo_init(
        Foo* self
        )
{
    self->foo_int = 10;
}

typedef struct _TestProp
{
    GObject parent_instance;
    gint p_int;
    gboolean p_bool;
    gchar* p_str;
    Foo* p_foo;
} TestProp;

typedef struct _TestPropClass
{
    GObjectClass parent_class;
} TestPropClass;

enum 
{
    PROP_0,
    PROP_P_INT,
    PROP_P_BOOL,
    PROP_P_STR,
    PROP_P_FOO,
    N_PROPERTIES
};

static GParamSpec* properties[N_PROPERTIES] = {NULL, };

G_DEFINE_TYPE (TestProp, test_prop, G_TYPE_OBJECT);

static void
test_prop_set_p_int(
        TestProp* obj,
        gint int_val
        )
{
    if (obj->p_int != int_val)
    {
        obj->p_int = int_val;
    }
}

static void
test_prop_set_p_bool(
        TestProp* obj,
        gboolean bool_val
        )
{
    if (obj->p_bool != bool_val)
    {
        obj->p_bool = bool_val;
    }
}

static void
test_prop_set_p_str(
        TestProp* obj,
        const gchar* str
        )
{
    if (g_strcmp0(obj->p_str, str) != 0)
    {
        g_free(obj->p_str);
        obj->p_str = g_strdup(str);
    }
}

static void
test_prop_set_p_foo(
        TestProp* tprop_obj,
        Foo* foo_obj
        )
{
    GValue obj1_val = {0,};
    GValue obj2_val = {0,};

    g_value_init(&obj1_val, foo_get_type());
    g_value_set_object(&obj1_val, foo_obj);

    g_value_init(&obj2_val, G_TYPE_OBJECT);

    g_value_copy(&obj1_val, &obj2_val);

    tprop_obj->p_foo = g_value_get_object(&obj2_val);
}

static void
test_prop_finalize(
        GObject* obj
        )
{
    g_free(((TestProp*)obj)->p_str);
//    g_free(((TestProp*)obj)->p_foo);

    G_OBJECT_CLASS(test_prop_parent_class)->finalize(obj);
}

static void
test_prop_set_property(
        GObject* gobject,
        guint prop_id,
        const GValue* value,
        GParamSpec* spec
        )
{
    TestProp* tprop = (TestProp*) gobject;

    switch(prop_id)
    {
        case PROP_P_INT:
            test_prop_set_p_int(tprop, g_value_get_int(value));
            break;
        case PROP_P_BOOL:
            test_prop_set_p_bool(tprop, g_value_get_boolean(value));
            break;
        case PROP_P_STR:
            test_prop_set_p_str(tprop, g_value_get_string(value));
            break;
        case PROP_P_FOO:
            test_prop_set_p_foo(tprop, g_value_get_object(value));
            break;
        default:
            g_assert_not_reached();
    }
}

static void
test_prop_get_property(
        GObject* gobject,
        guint prop_id,
        GValue* value,
        GParamSpec* spec
        )
{
    TestProp* tprop = (TestProp*) gobject;
//    GValue obj_val = {0,};

    switch(prop_id)
    {
        case PROP_P_INT:
            g_value_set_int(value, tprop->p_int);
            break;
        case PROP_P_BOOL:
            g_value_set_boolean(value, tprop->p_bool);
            break;
        case PROP_P_STR:
            g_value_set_string(value, tprop->p_str);
            break;
        case PROP_P_FOO:
            log;
            g_value_set_object(value, tprop->p_foo);
            break;
        default:
            g_assert_not_reached();
    }
}

static void
test_prop_class_init(
        TestPropClass* klass
        )
{
    GObjectClass* gobject_class = G_OBJECT_CLASS(klass);

    properties[PROP_P_INT] = g_param_spec_int(
            "p_int", 
            "P_int", 
            "P_int", 
            -1,
            G_MAXINT, 
            0, 
            G_PARAM_READWRITE);

    properties[PROP_P_BOOL] = g_param_spec_boolean(
            "p_bool",
            "P_bool",
            "P_bool",
            FALSE,
            G_PARAM_READWRITE
            );

    properties[PROP_P_STR] = g_param_spec_string(
            "p_str",
            "P_str",
            "P_str",
            NULL,
            G_PARAM_READWRITE
            );

    properties[PROP_P_FOO] = g_param_spec_object(
            "p_foo",
            "P_foo",
            "P_foo",
            foo_get_type(),
            G_PARAM_READWRITE
            );

    gobject_class->set_property = test_prop_set_property;
    gobject_class->get_property = test_prop_get_property;
    gobject_class->finalize = test_prop_finalize;

    g_object_class_install_properties(gobject_class, N_PROPERTIES, properties);
}

static void
test_prop_init(
        TestProp* self
        )
{
    self->p_int = 99;
    self->p_bool = TRUE;
    self->p_str = g_strdup("");
    self->p_foo = g_object_new(foo_get_type(), NULL);
}
    
int
main (int argc, char* argv[])
{
    g_type_init();
   
    TestProp* obj = g_object_new(test_prop_get_type(), NULL);
    GParamSpec* pspec;
    gint val;
    Foo* foo_obj = g_object_new(foo_get_type(), NULL);
    GObject* objval;

    foo_obj->foo_int = 25;

    pspec = g_object_class_find_property(G_OBJECT_GET_CLASS (obj), "p_int");
    g_assert(properties[PROP_P_INT] == pspec);

    g_object_set(obj, "p_int", 55, NULL);
    g_object_get(obj,"p_int", &val, NULL);
    g_printf("val: %d\n", val);

    g_object_set(obj,"p_foo", foo_obj, NULL);

    g_object_get(obj,"p_foo", &objval, NULL);
    g_printf("foo_int: %d\n", ((Foo*)objval)->foo_int);

    g_object_unref(obj);

    return 0;
}

